/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
using System;
using java = biz.ritter.javapi;

namespace org.apache.commons.compress.archivers.zip {

    /**
     * This ZipEncoding implementation implements a simple 8bit character
     * set, which mets the following restrictions:
     * 
     * <ul>
     * <li>Characters 0x0000 to 0x007f are encoded as the corresponding
     *        byte values 0x00 to 0x7f.</li>
     * <li>All byte codes from 0x80 to 0xff are mapped to a unique unicode
     *       character in the range 0x0080 to 0x7fff. (No support for
     *       UTF-16 surrogates)
     * </ul>
     * 
     * <p>These restrictions most notably apply to the most prominent
     * omissions of java-1.4's {@link java.nio.charset.Charset Charset}
     * implementation, Cp437 and Cp850.</p>
     * 
     * <p>The methods of this class are reentrant.</p>
     * @Immutable
     */
    internal class Simple8BitZipEncoding : ZipEncoding {

        /**
         * A character entity, which is put to the reverse mapping table
         * of a simple encoding.
         */
        /**
         * The characters for byte values of 128 to 255 stored as an array of
         * 128 chars.
         */
        private readonly char[] highChars;

        /**
         * A list of {@link Simple8BitChar} objects sorted by the unicode
         * field.  This list is used to binary search reverse mapping of
         * unicode characters with a character code greater than 127.
         */
        private readonly java.util.List<Simple8BitChar> reverseMapping;

        /**
         * @param highChars The characters for byte values of 128 to 255
         * stored as an array of 128 chars.
         */
        public Simple8BitZipEncoding(char[] highChars) {
            this.highChars = (char[]) highChars.clone();
            java.util.List<Simple8BitChar> temp = new java.util.ArrayList<Simple8BitChar>(this.highChars.Length);

            byte code = 127;

            for (int i = 0; i < this.highChars.Length; ++i) {
                temp.add(new Simple8BitChar(++code, this.highChars[i]));
            }

            java.util.Collections<Simple8BitChar>.sort(temp);
            this.reverseMapping = java.util.Collections<Object>.unmodifiableList(temp);
        }

        /**
         * Return the character code for a given encoded byte.
         * 
         * @param b The byte to decode.
         * @return The associated character value.
         */
        public char decodeByte(byte b) {
            // code 0-127
            if (b >= 0) {
                return (char) b;
            }

            // byte is signed, so 128 == -128 and 255 == -1
            return this.highChars[128 + b];
        }

        /**
         * @param c The character to encode.
         * @return Whether the given unicode character is covered by this encoding.
         */
        public bool canEncodeChar(char c) {

            if (c >= 0 && c < 128) {
                return true;
            }

            Simple8BitChar r = this.encodeHighChar(c);
            return r != null;
        }

        /**
         * Pushes the encoded form of the given character to the given byte buffer.
         * 
         * @param bb The byte buffer to write to.
         * @param c The character to encode.
         * @return Whether the given unicode character is covered by this encoding.
         *         If <code>false</code> is returned, nothing is pushed to the
         *         byte buffer. 
         */
        public bool pushEncodedChar(java.nio.ByteBuffer bb, char c) {

            if (c >= 0 && c < 128) {
                bb.put((byte) c);
                return true;
            }

            Simple8BitChar r = this.encodeHighChar(c);
            if (r == null) {
                return false;
            }
            bb.put(r.code);
            return true;
        }

        /**
         * @param c A unicode character in the range from 0x0080 to 0x7f00
         * @return A Simple8BitChar, if this character is covered by this encoding.
         *         A <code>null</code> value is returned, if this character is not
         *         covered by this encoding.
         */
        private Simple8BitChar encodeHighChar(char c) {
            // for performance an simplicity, yet another reincarnation of
            // binary search...
            int i0 = 0;
            int i1 = this.reverseMapping.size();

            while (i1 > i0) {

                int i = i0 + (i1 - i0) / 2;

                Simple8BitChar m = (Simple8BitChar) this.reverseMapping.get(i);

                if (m.unicode == c) {
                    return m;
                }

                if (m.unicode < c) {
                    i0 = i + 1;
                } else {
                    i1 = i;
                }
            }

            if (i0 >= this.reverseMapping.size()) {
                return null;
            }

            Simple8BitChar r = (Simple8BitChar) this.reverseMapping.get(i0);

            if (r.unicode != c) {
                return null;
            }

            return r;
        }

        /**
         * @see
         * org.apache.commons.compress.archivers.zip.ZipEncoding#canEncode(java.lang.String)
         */
        public bool canEncode(String name) {

            for (int i=0;i<name.length();++i) {

                char c = name.charAt(i);

                if (!this.canEncodeChar(c)) {
                    return false;
                }
            }

            return true;
        }

        /**
         * @see
         * org.apache.commons.compress.archivers.zip.ZipEncoding#encode(java.lang.String)
         */
        public java.nio.ByteBuffer encode(String name) {
            java.nio.ByteBuffer outJ = java.nio.ByteBuffer.allocate(name.length()
                                                 + 6 + (name.length() + 1) / 2);

            for (int i=0;i<name.length();++i) {

                char c = name.charAt(i);

                if (outJ.remaining() < 6) {
                    outJ = ZipEncodingHelper.growBuffer(outJ,outJ.position() + 6);
                }

                if (!this.pushEncodedChar(outJ,c)) {

                    ZipEncodingHelper.appendSurrogate(outJ,c);
                }
            }

            outJ.limit(outJ.position());
            outJ.rewind();
            return outJ;
        }

        /**
         * @see
         * org.apache.commons.compress.archivers.zip.ZipEncoding#decode(byte[])
         */
        public String decode(byte[] data)// throws IOException 
        {
            char [] ret = new char[data.Length];

            for (int i=0;i<data.Length;++i) {
                ret[i] = this.decodeByte(data[i]);
            }

            return new String(ret);
        }


    }
    internal sealed class Simple8BitChar : java.lang.Comparable<Simple8BitChar> {
        public readonly char unicode;
        public readonly byte code;

        internal Simple8BitChar(byte code, char unicode) {
            this.code = code;
            this.unicode = unicode;
        }

        public int compareTo(Simple8BitChar o)
        {
            return this.unicode - o.unicode;
        }

        public override String ToString() {
            return "0x" + java.lang.Integer.toHexString(0xffff & unicode)
                + "->0x" + java.lang.Integer.toHexString(0xff & code);
        }
    }

}